'''
Created on Sep 15, 2009

@author: sax
'''
from django.contrib.admin.options import InlineModelAdmin, ModelAdmin, BaseModelAdmin, TabularInline
from django import forms
from django.contrib.admin.util import flatten_fieldsets
from django.core import urlresolvers
from django.utils.safestring import mark_safe
from django.forms.models import _get_foreign_key
from django.db import models
from django.core.exceptions import ObjectDoesNotExist
from django.utils.encoding import smart_unicode, smart_str, force_unicode
from django.utils.html import escape, conditional_escape

EMPTY_CHANGELIST_VALUE = '(None)'
      
#InlineAdminForm
class DetailAdminRow(object):
    
    def __init__(self, obj, model_admin):
        self.obj = obj
        self.fields = model_admin.list_display 
        self.opts = model_admin.model._meta
        self.model_admin = model_admin
        
        try:
            self.change_url = urlresolvers.reverse('admin:%s_%s_change' % (self.obj._meta.app_label, self.obj._meta.module_name), args=(self.obj.pk,))            
        except:
            self.change_url = None
        
    def __iter__(self):
        first = True
        result = self.obj
        for field_name in  self.fields:
            try:
                f = self.opts.get_field(field_name)
            except models.FieldDoesNotExist:
                # For non-field list_display values, the value is either a method,
                # property or returned via a callable.
                try:
                    if callable(field_name):
                        attr = field_name
                        value = attr(result)
                    elif hasattr(self.model_admin, field_name) and \
                       not field_name == '__str__' and not field_name == '__unicode__':
                        attr = getattr(self.model_admin, field_name)
                        value = attr()
                    else:
                        attr = getattr(result, field_name)
                        if callable(attr):
                            value = attr()
                        else:
                            value = attr
                    allow_tags = getattr(attr, 'allow_tags', False)
                    boolean = getattr(attr, 'boolean', False)
                    if boolean:
                        allow_tags = True
                        result_repr = _boolean_icon(value)
                    else:
                        result_repr = smart_unicode(value)
                except (AttributeError, ObjectDoesNotExist), e:
                    result_repr = e#EMPTY_CHANGELIST_VALUE
                else:
                    # Strip HTML tags in the resulting text, except if the
                    # function has an "allow_tags" attribute set to True.
                    if not allow_tags:
                        result_repr = escape(result_repr)
                    else:
                        result_repr = mark_safe(result_repr)
            else:
                field_val = getattr(result, f.attname)
                if isinstance(f.rel, models.ManyToOneRel):
                    if field_val is not None:
                        result_repr = escape(getattr(result, f.name))
                    else:
                        result_repr = EMPTY_CHANGELIST_VALUE
                # Dates and times are special: They're formatted in a certain way.
                elif isinstance(f, models.DateField) or isinstance(f, models.TimeField):
                    if field_val:
                        (date_format, datetime_format, time_format) = get_date_formats()
                        if isinstance(f, models.DateTimeField):
                            result_repr = capfirst(dateformat.format(field_val, datetime_format))
                        elif isinstance(f, models.TimeField):
                            result_repr = capfirst(dateformat.time_format(field_val, time_format))
                        else:
                            result_repr = capfirst(dateformat.format(field_val, date_format))
                    else:
                        result_repr = EMPTY_CHANGELIST_VALUE
                    row_class = ' class="nowrap"'
                # Booleans are special: We use images.
                elif isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField):
                    result_repr = _boolean_icon(field_val)
                # DecimalFields are special: Zero-pad the decimals.
                elif isinstance(f, models.DecimalField):
                    if field_val is not None:
                        result_repr = ('%%.%sf' % f.decimal_places) % field_val
                    else:
                        result_repr = EMPTY_CHANGELIST_VALUE
                # Fields with choices are special: Use the representation
                # of the choice.
                elif f.flatchoices:
                    result_repr = dict(f.flatchoices).get(field_val, EMPTY_CHANGELIST_VALUE)
                else:
                    result_repr = escape(field_val)
            if force_unicode(result_repr) == '':
                result_repr = mark_safe('&nbsp;')
            
            if field_name in self.model_admin.list_display_links:
                #result_repr = mark_safe("<a href='{0}'>{1}<a>".format(self.change_url, result_repr))
                result_repr = mark_safe("<a href='%s'>%s<a>" % (self.change_url, result_repr))
            
            yield result_repr    
#            # If list_display_links not defined, add the link tag to the first field
#            if (first and not self.model_admin.list_display_links) or field_name in self.model_admin.list_display_links:
#                table_tag = {True:'th', False:'td'}[first]
#                first = False
#                url = self.url_for_result(result)
#                # Convert the pk to something that can be used in Javascript.
#                # Problem cases are long ints (23L) and non-ASCII strings.
#                attr = pk
#                value = result.serializable_value(attr)
#                result_id = repr(force_unicode(value))[1:]
#                yield mark_safe(u'<%s%s><a href="%s"%s>%s</a></%s>' % \
#                    (table_tag, row_class, url, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %s); return false;"' % result_id or ''), conditional_escape(result_repr), table_tag))
#            else:
#                # By default the fields come from ModelAdmin.list_editable, but if we pull
#                # the fields out of the form instead of list_editable custom admins
#                # can provide fields on a per request basis
#                if form and field_name in form.fields:
#                    bf = form[field_name]
#                    result_repr = mark_safe(force_unicode(bf.errors) + force_unicode(bf))
#                else:
#                    result_repr = conditional_escape(result_repr)
#                yield mark_safe(u'<td%s>%s</td>' % (row_class, result_repr))
#        if form:
#            yield mark_safe(force_unicode(form[cl.model._meta.pk.name]))            

    def url_for_result(self, result):
        return "%s/" % quote(getattr(result, self.pk_attname))

#            if callable(field):
#                yield field(self.obj)
#            else:
#                field_name = field.name
#                field, model, direct, m2m  = self.obj._meta.get_field_by_name(field_name)
#                label = getattr(self.obj, "get_%s_display" % field_name, None)
#                if label:
#                    label = label()
#                else:
#                    label = getattr(self.obj, field_name)                
#                if self.change_url:            
#                    yield mark_safe("<a href='{0}'>{1}<a>".format(self.change_url, label))
#                else:
#                    yield label
                
#InlineAdminFormSet            
class DetailAdminSet(object):
    
    def __init__(self, detail_model_admin,  parent_obj):                  
        self.model_admin = detail_model_admin
        self.opts = detail_model_admin
        self.parent_obj = parent_obj
        
        #self.field_names = flatten_fieldsets(fieldsets) or detail_model_admin.list_display
        self.fk = _get_foreign_key(self.model_admin.parent_model, self.model_admin.model, fk_name=self.model_admin.fk_name)        
        set_name = self.fk.related.get_accessor_name()
        self.datas = []
        if parent_obj:
            if self.model_admin.max_num > 0:
                self.datas = getattr(parent_obj, set_name).all()[:self.model_admin.max_num]
            else:
                self.datas = getattr(parent_obj, set_name).all()        
    
    def __repr__(self):
        return "%s %s (%d)" % (self.__class__.__name__, self.model_admin, len(self.datas) )
    
    def _columns(self):
        lookup_opts = self.model_admin.model._meta
        for i, field_name in enumerate(self.model_admin.list_display):
            attr = None
            try:
                f = lookup_opts.get_field(field_name)
                admin_order_field = None
            except models.FieldDoesNotExist:
                # For non-field list_display values, check for the function
                # attribute "short_description". If that doesn't exist, fall back
                # to the method name. And __str__ and __unicode__ are special-cases.
                if field_name == '__unicode__':
                    header = force_unicode(lookup_opts.verbose_name)
                elif field_name == '__str__':
                    header = smart_str(lookup_opts.verbose_name)
                else:
                    if callable(field_name):
                        attr = field_name # field_name can be a callable
                    else:
                        try:
                            attr = getattr(self.model_admin, field_name)
                        except AttributeError:
                            try:
                                attr = getattr(self.model_admin.model, field_name)
                            except AttributeError:
                                raise AttributeError, \
                                    "'%s' model or '%s' objects have no attribute '%s'" % \
                                        (lookup_opts.object_name, self.model_admin.__class__, field_name)

                    try:
                        header = attr.short_description
                    except AttributeError:
                        if callable(field_name):
                            header = field_name.__name__
                        else:
                            header = field_name
                        header = header.replace('_', ' ')

            else:
                header = f.verbose_name
            yield header
            
    def _get_field(self, field_name):
        field_object = None
        # try with fieldname
        try:
            field_object, model, direct, m2m = self.model_admin.model._meta.get_field_by_name(field_name)
        except Exception:
            # try id field_name is method of model
            func = getattr(self.model_admin.model, field_name, None)
            if func:
                field_object = MethodWrapper( func, None )    
            else:
            # try id field_name is method of modelAdmin
                func = getattr(self.model_admin, field_name)             
                field_object = MethodWrapper( func, self.model_admin )
            
        return field_object
    
    def x_columns(self):
        for col in self.model_admin.list_display:
            field_object = self._get_field(col)            
            if field_object == self.fk:
                self.field_names.remove(col)
                continue            
            yield field_object              
    columns = property(_columns)
                
    def __iter__(self):
        for row in self.datas:
            yield DetailAdminRow(row, self.model_admin)

# InlineModelAdmin
class DetailModelAdmin(TabularInline, BaseModelAdmin):
    pass
#    ro_template = 'xadmin/inlines/read_only.html'
#    detailset = DetailAdminSet
#    max_num = 0
#    list_display = ('__str__',)
#    verbose_name = None
#    verbose_name_plural = None
#    fk_name = None
#    list_display_links = None
#    extra = 1
#   
#    def x__init__(self, parent_model, admin_site):
#        self.admin_site = admin_site
#        self.parent_model = parent_model
#        self.opts = self.model._meta
# 
#        if not self.list_display_links:
#            for name in self.list_display:
#                self.list_display_links = [name]
#                break
#        
#        if self.verbose_name is None:
#            self.verbose_name = self.model._meta.verbose_name
#        if self.verbose_name_plural is None:
#            self.verbose_name_plural = self.model._meta.verbose_name_plural


class Extras(DetailModelAdmin):
    pass
















    